package spacewar2.clients;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import spacewar2.actions.SpacewarAction;
import spacewar2.actions.SpacewarPurchaseEnum;
import spacewar2.objects.Base;
import spacewar2.objects.Ship;
import spacewar2.objects.SpacewarActionableObject;
import spacewar2.powerups.SpacewarPowerup;
import spacewar2.powerups.SpacewarPowerupEnum;
import spacewar2.shadows.Shadow;
import spacewar2.simulator.SpacewarSimulator;
import spacewar2.simulator.Toroidal2DPhysics;

/**
 * A team holds the ships and a pointer to the client
 * that controls the ships.  They are separated for security inside
 * the client (to keep the client from directly manipulating ships)
 * 
 * @author amy
 */
public class Team {
	ArrayList<Ship> teamShips;
	
	ArrayList<UUID> teamBaseIDs;

	/**
	 * The team color (used by the GUI)
	 */
	Color teamColor;
	
	/**
	 * The name of the team
	 */
	String teamName;
	
	/**
	 * The client for this team
	 */
	TeamClient teamClient;
	
	/**
	 * current team score (set in the simulator, which knows how the team is being scored)
	 */
	double score;
	
	/**
	 * available (unspent) money from the asteroids and the total money earned
	 */
	int availableMoney, totalMoney;
	
	/**
	 * Keep track of the total beacons collected (for the ladder)
	 */
	int totalBeacons;
	
	/**
	 * The name that shows up in the ladder
	 */
	String ladderName;
	
	/**
	 * The current cost for this team to buy a base
	 */
	int currentCostNewBase;
	

	/**
	 * Initialize the team client to have an empty list of ships.
	 */
	public Team(TeamClient teamClient, String ladderName) {
		this.teamShips = new ArrayList<Ship>();
		this.teamBaseIDs = new ArrayList<UUID>();
		this.teamClient = teamClient;
		this.teamColor = teamClient.getTeamColor();
		this.teamName = teamClient.getTeamName();
		this.ladderName = ladderName;
		this.currentCostNewBase = Base.INITIAL_COST;
	}
	
	/**
	 * Make a deep copy for security
	 * 
	 * @return
	 */
	public Team deepCopy() {
		Team newTeam = new Team(teamClient, ladderName);
		
		for (Ship ship : teamShips) {
			newTeam.addShip(ship.deepClone());
		}
		
		newTeam.teamBaseIDs.addAll(this.teamBaseIDs);
		newTeam.currentCostNewBase = currentCostNewBase;
		return newTeam;
	}
	
	/**
	 * Make a cloned list of the team ships
	 * 
	 * @return
	 */
	private Set<Ship> getTeamShipsClone() {
		Set<Ship> clonedShips = new HashSet<Ship>();
		
		for (Ship ship : teamShips) {
			clonedShips.add(ship.deepClone());
		}
		return clonedShips;
	}
	
	/**
	 * Make a cloned list of the team's actionable objects (ships and bases)
	 * 
	 * @return
	 */
	private Set<SpacewarActionableObject> getTeamActionableObjectsClone(Toroidal2DPhysics space) {
		Set<SpacewarActionableObject> clones = new HashSet<SpacewarActionableObject>();
		
		for (Ship ship : teamShips) {
			clones.add(ship.deepClone());
		}
		
		for (UUID baseId : teamBaseIDs) {
			clones.add((Base)space.getObjectById(baseId));
		}
		
		return clones;
	}

	
	
	/**
	 * Add a ship to the team
	 * @param ship
	 */
	public void addShip(Ship ship) {
		teamShips.add(ship);
	}
	
	/**
	 * Return the list of ships
	 * @return
	 */
	public ArrayList<Ship> getShips() {
		return teamShips;
	}

	/**
	 * Add a base to the team's list of bases
	 * 
	 * @param base
	 */
	public void addBase(Base base) {
		teamBaseIDs.add(base.getId());
	}
	
	/**
	 * Remove the base because it died
	 * @param base
	 */
	public void removeBase(Base base) {
		teamBaseIDs.remove(base.getId());
	}
	
	/**
	 * @return the teamColor
	 */
	public Color getTeamColor() {
		return teamColor;
	}

	/**
	 * @param teamColor the teamColor to set
	 */
	public void setTeamColor(Color teamColor) {
		this.teamColor = teamColor;
	}

	/**
	 * @return the teamName
	 */
	public String getTeamName() {
		return teamName;
	}

	/**
	 * @param teamName the teamName to set
	 */
	public void setTeamName(String teamName) {
		this.teamName = teamName;
	}

	/**
	 * Get the current cost of a new base
	 * @return
	 */
	public int getCurrentCostNewBase() {
		return currentCostNewBase;
	}

	/**
	 * Change the cost of a base for this team (done after a purchase)
	 */
	public void setCurrentCostNewBase(int currentCostNewBase) {
		this.currentCostNewBase = currentCostNewBase;
	}

	/**
	 * Ask the team client for actions
	 * 
	 * @param spacewarSimulator
	 * @param random
	 * @return
	 */
	public Map<UUID, SpacewarAction> getClientMovementStart(Toroidal2DPhysics space) {
		final Toroidal2DPhysics clonedSpace = space.deepClone();
		final Set<SpacewarActionableObject> clonedActionableObjects = getTeamActionableObjectsClone(space);
		
		ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Map<UUID, SpacewarAction>> future = executor.submit(
        		new Callable<Map<UUID, SpacewarAction>>(){
        			public Map<UUID, SpacewarAction> call() throws Exception {
        				return teamClient.getMovementStart(clonedSpace, clonedActionableObjects);
        			}
        		});
        
        Map<UUID, SpacewarAction> teamActions = null;
        try {
            //start
            teamActions = future.get(SpacewarSimulator.TEAM_ACTION_TIMEOUT, TimeUnit.MILLISECONDS);
            //finished in time
        } catch (TimeoutException e) {
            //was terminated
        	//return empty map, this will invoke default behavior of using DoNothingAction
        	teamActions = new HashMap<UUID, SpacewarAction>();
        } catch (InterruptedException e) {
        	//we were interrupted (should not happen but lets be good programmers) 
        	//return empty map, this will invoke default behavior of using DoNothingAction
        	teamActions = new HashMap<UUID, SpacewarAction>();
			e.printStackTrace();
		} catch (ExecutionException e) {
			//the executor threw and exception (should not happen but lets be good programmers) 
        	//return empty map, this will invoke default behavior of using DoNothingAction
        	teamActions = new HashMap<UUID, SpacewarAction>();
		}

        executor.shutdownNow();
        
        return teamActions;
		
// 		HashMap<UUID, SpacewarAction> teamActions = teamClient.getAction(clonedSpace, clonedTeamShips);
//		return teamActions;
	}

	/**
	 * Allows the client to do cleanup after an action and before
	 * the next one (if needed)
	 * 
	 * @param simulatedSpace
	 * @return
	 */
	public void getClientMovementEnd(Toroidal2DPhysics space) {
		final Toroidal2DPhysics clonedSpace = space.deepClone();
		final Set<SpacewarActionableObject> clonedActionableObjects = getTeamActionableObjectsClone(space);
		
		ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Boolean> future = executor.submit(
        		new Callable<Boolean>(){
        			public Boolean call() throws Exception {
        				teamClient.getMovementEnd(clonedSpace, clonedActionableObjects);
        				return true;
        			}
        		});
        
        Boolean didReturn = false;
        try {
            //start
        	didReturn = future.get(SpacewarSimulator.TEAM_END_ACTION_TIMEOUT, TimeUnit.MILLISECONDS);
            //finished in time
        } catch (TimeoutException e) {
            //was terminated
        	//set didReturn false
        	didReturn = false;
        } catch (InterruptedException e) {
        	//we were interrupted (should not happen but lets be good programmers) 
        	//set didReturn false
        	didReturn = false;
			e.printStackTrace();
		} catch (ExecutionException e) {
			//the executor threw and exception (should not happen but lets be good programmers) 
			//set didReturn false
        	didReturn = false;
		}

        executor.shutdownNow();
		
		// figure out how many beacons the team has collected
		int beacons = 0;
		for (Ship ship : teamShips) {
			beacons += ship.getNumBeacons();
		}
		setTotalBeacons(beacons);
	}
	
	/**
	 * Ask the team if they want to purchase anything this turn.  You can only 
	 * purchase one item per turn and only if you have enough money.
	 * 
	 * @param space
	 * @return
	 */
	public Map<UUID,SpacewarPurchaseEnum> getTeamPurchases(Toroidal2DPhysics space) {
		final Toroidal2DPhysics clonedSpace = space.deepClone();
		final Set<Ship> clonedTeamShips = getTeamShipsClone();
		
		ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Map<UUID,SpacewarPurchaseEnum>> future = executor.submit(
        		new Callable<Map<UUID,SpacewarPurchaseEnum>>(){
        			public Map<UUID,SpacewarPurchaseEnum> call() throws Exception {
        				return teamClient.getTeamPurchases(clonedSpace, clonedTeamShips, availableMoney, currentCostNewBase);
        			}
        		});
        
        Map<UUID,SpacewarPurchaseEnum> purchase = null;
        try {
            //start
        	purchase = future.get(SpacewarSimulator.TEAM_ACTION_TIMEOUT, TimeUnit.MILLISECONDS);
            //finished in time
        } catch (TimeoutException e) {
            //was terminated
        	//return empty map, don't buy anything
        	purchase = new HashMap<UUID,SpacewarPurchaseEnum>();
        } catch (InterruptedException e) {
        	//we were interrupted (should not happen but lets be good programmers) 
        	//return empty map, don't buy anything
        	purchase = new HashMap<UUID,SpacewarPurchaseEnum>();
			e.printStackTrace();
		} catch (ExecutionException e) {
			//the executor threw and exception (should not happen but lets be good programmers) 
        	//return empty map, don't buy anything
        	purchase = new HashMap<UUID,SpacewarPurchaseEnum>();
		}

        executor.shutdownNow();
        
        return purchase;
	}

	/**
	 * Get the weapons or power ups for the team this turn
	 * 
	 * @param space
	 * @return
	 */
	public Map<UUID, SpacewarPowerup> getTeamPowerups(Toroidal2DPhysics space) {
		final Toroidal2DPhysics clonedSpace = space.deepClone();
		final Set<SpacewarActionableObject> clonedActionableObjects = getTeamActionableObjectsClone(space);
		
		ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Map<UUID,SpacewarPowerup>> future = executor.submit(
        		new Callable<Map<UUID,SpacewarPowerup>>(){
        			public Map<UUID,SpacewarPowerup> call() throws Exception {
        				return teamClient.getPowerups(clonedSpace, clonedActionableObjects);
        			}
        		});
        
        Map<UUID,SpacewarPowerup> powerups = null;
        try {
            //start
        	powerups = future.get(SpacewarSimulator.TEAM_ACTION_TIMEOUT, TimeUnit.MILLISECONDS);
            //finished in time
        } catch (TimeoutException e) {
            //was terminated
        	//return empty map, don't buy anything
        	powerups = new HashMap<UUID,SpacewarPowerup>();
        } catch (InterruptedException e) {
        	//we were interrupted (should not happen but lets be good programmers) 
        	//return empty map, don't buy anything
        	powerups = new HashMap<UUID,SpacewarPowerup>();
			e.printStackTrace();
		} catch (ExecutionException e) {
			//the executor threw and exception (should not happen but lets be good programmers) 
        	//return empty map, don't buy anything
			powerups = new HashMap<UUID,SpacewarPowerup>();
		}

        executor.shutdownNow();
        
        return powerups;
	}


	/**
	 * Get any new shadows the team client wants to draw
	 * 
	 * @return
	 */
	public Set<Shadow> getNewShadows() {
		ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Set<Shadow>> future = executor.submit(
        		new Callable<Set<Shadow>>(){
        			public Set<Shadow> call() throws Exception {
        				return teamClient.getNewShadows();
        			}
        		});
        
        Set<Shadow> shadows = null;
        try {
            //start
        	shadows = future.get(SpacewarSimulator.TEAM_SHADOW_TIMEOUT, TimeUnit.MILLISECONDS);
            //finished in time
        } catch (TimeoutException e) {
            //was terminated
        	//set empty array of shadows
        	shadows = new HashSet<Shadow>();
        } catch (InterruptedException e) {
        	//we were interrupted (should not happen but lets be good programmers) 
        	//set empty array of shadows
        	shadows = new HashSet<Shadow>();
		} catch (ExecutionException e) {
			//the executor threw and exception (should not happen but lets be good programmers) 
			//set empty array of shadows
        	shadows = new HashSet<Shadow>();
		}

        executor.shutdownNow();
        
		return shadows;
	}

	/**
	 * Get any old shadows the team client wants to remove from the GUI
	 * @return
	 */
	public Set<Shadow> getOldShadows() {
		ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Set<Shadow>> future = executor.submit(
        		new Callable<Set<Shadow>>(){
        			public Set<Shadow> call() throws Exception {
        				return teamClient.getOldShadows();
        			}
        		});
        
        Set<Shadow> shadows = null;
        try {
            //start
        	shadows = future.get(SpacewarSimulator.TEAM_SHADOW_TIMEOUT, TimeUnit.MILLISECONDS);
            //finished in time
        } catch (TimeoutException e) {
            //was terminated
        	//set empty array of shadows
        	shadows = new HashSet<Shadow>();
        } catch (InterruptedException e) {
        	//we were interrupted (should not happen but lets be good programmers) 
        	//set empty array of shadows
        	shadows = new HashSet<Shadow>();
		} catch (ExecutionException e) {
			//the executor threw and exception (should not happen but lets be good programmers) 
			//set empty array of shadows
        	shadows = new HashSet<Shadow>();
		}

        executor.shutdownNow();
        
		return shadows;
	}

	/**
	 * Get the current team score
	 * @return
	 */
	public double getScore() {
		return score;
	}

	/**
	 * Set the current team score
	 * @param score
	 */
	public void setScore(double score) {
		this.score = score;
	}
	
	public void incrementTotalMoney(double difference) {
		totalMoney += difference;
	}

	public int getTotalMoney() {
		return totalMoney;
	}
	
	public void incrementAvailableMoney(double difference) {
		availableMoney += difference;
	}

	public int getAvailableMoney() {
		return availableMoney;
	}

	public int getTotalBeacons() {
		return totalBeacons;
	}

	public void setTotalBeacons(int totalBeacons) {
		this.totalBeacons = totalBeacons;
	}
	
	public void incrementTotalBeacons() {
		this.totalBeacons++;
	}
	
	

	public String getLadderName() {
		return ladderName;
	}

	/**
	 * Called at the end of a simulation to cleanup the clients
	 */
	public void shutdownClients() {
		teamClient.shutDown();
	}
	
}
